package org.yihang.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.yihang.annotation.*;
import org.yihang.annotation.easyui.*;
import org.yihang.util.EasyUIMeta.UIMethod;
import org.yihang.util.Meta.Structure;

import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;

import static org.yihang.util.MapperGenerator.*;

public class EasyUIGenerator {

    private EasyUIGenerator() {
    }

    private static final String EMPTY = "";
    private Structure structure;
    private String outputDir;
    private String idProperty;
    private String htmlDir;
    private String appPath;
    private Set<UIMethod> methods = new LinkedHashSet<>();
    private String voPackage;
    private String controllerPackage;
    private String mapperPackage;

    public static class Builder {
        private EasyUIGenerator semi;

        public Builder() {
            semi = new EasyUIGenerator();
            semi.structure = Structure.Maven;
            semi.outputDir = System.getProperty("user.dir");
            semi.idProperty = "id";
            semi.htmlDir = "";
            semi.appPath = "";
            semi.methods.addAll(Arrays.asList(UIMethod.values()));
        }

        public Builder setStructure(Structure structure) {
            semi.structure = structure;
            return this;
        }

        public Builder setOutputDir(String outputDir) {
            semi.outputDir = outputDir;
            return this;
        }

        public Builder setIdProperty(String idProperty) {
            semi.idProperty = idProperty;
            return this;
        }

        public Builder setHtmlDir(String htmlDir) {
            semi.htmlDir = htmlDir;
            return this;
        }

        public Builder setAppPath(String appPath) {
            semi.appPath = appPath;
            return this;
        }

        public Builder setVoPackage(String voPackage) {
            semi.voPackage = voPackage;
            return this;
        }

        public Builder setControllerPackage(String controllerPackage) {
            semi.controllerPackage = controllerPackage;
            return this;
        }

        public Builder setMapperPackage(String mapperPackage) {
            semi.mapperPackage = mapperPackage;
            return this;
        }

        public Builder include(UIMethod... methods) {
            if (methods != null && methods.length > 0) {
                semi.methods.clear();
                semi.methods.addAll(Arrays.asList(methods));
            }
            return this;
        }

        public Builder exclude(UIMethod... methods) {
            if (methods != null && methods.length > 0) {
                semi.methods.removeAll(Arrays.asList(methods));
            }
            return this;
        }

        public EasyUIGenerator build() {
            return semi;
        }
    }

    public void process(Class<?> c) {
        try {
            Map<String, Object> map = new HashMap<>();
            map.put("class", c.getName()); // 类全名
            String class_s = c.getSimpleName();
            map.put("class_s", class_s); // 类简名
            String class_ss = Character.toLowerCase(c.getSimpleName().charAt(0)) + c.getSimpleName().substring(1);
            map.put("class_ss", class_ss); // 类名作为参数时名称
            map.put("root", appPath);
            map.put("toolbar", true);
            map.put("useAdapter", false);
            map.put("treeAdapter", false);

            Field[] fields = c.getDeclaredFields();
            // 处理id
            Optional<Field> idOptional = Arrays.stream(fields)
                    .filter(f -> f.getName().equals(idProperty) || f.getAnnotation(Id.class) != null).findFirst();
            if (!idOptional.isPresent()) {
                throw new RuntimeException("no @Id field!");
            }
            Field id = idOptional.get();
            map.put("id_property", id.getName());
            map.put("id_class", id.getType().getCanonicalName());
            map.put("id_class_boxed", getBoxedType(id.getType()).getCanonicalName());

            if (this.voPackage == null) {
                this.voPackage = c.getPackage().getName() + ".vo";
            }
            map.put("vopackage", this.voPackage);
            map.put("controllerPackage", this.controllerPackage);
            map.put("mapperPackage", this.mapperPackage);
            map.put("mapper", c.getSimpleName() + "Mapper");
            String mapper_ss = Character.toLowerCase(c.getSimpleName().charAt(0)) + c.getSimpleName().substring(1) + "Mapper";
            map.put("mapper_ss", mapper_ss);

            map.put("save", this.methods.contains(UIMethod.SAVE));
            map.put("update", this.methods.contains(UIMethod.UPDATE));
            map.put("deleteById", this.methods.contains(UIMethod.DELETE_BY_ID));
            map.put("deleteByIds", this.methods.contains(UIMethod.DELETE_BY_IDS));
            map.put("findById", this.methods.contains(UIMethod.FIND_BY_ID));
            map.put("findAll", this.methods.contains(UIMethod.FIND_ALL));
            map.put("findAllKV", this.methods.contains(UIMethod.FIND_ALL_KV));
            map.put("datagrid", this.methods.contains(UIMethod.DATA_GRID));


            // 检查 findById
            FindBy findByIdAnno = id.getAnnotation(FindBy.class);
            if (this.methods.contains(UIMethod.FIND_BY_ID)) {
                if (findByIdAnno == null || findByIdAnno.onlyMulti()) {
                    throw new RuntimeException("mapper需要提供findById方法");
                }
                ByProperty byIdProperty = new ByProperty("findBy" + firstUpper(id.getName()), id.getName(), null,
                        id.getType().getCanonicalName(), class_s, null);
                map.put("findByIdProperty", byIdProperty);
            }
            // 检查 update
            if (this.methods.contains(UIMethod.UPDATE)) {
                if (findByIdAnno == null || findByIdAnno.onlyMulti()) {
                    throw new RuntimeException("mapper需要提供findById方法，update操作要用到");
                }
            }
            // 检查 deleteById
            DeleteBy deleteByAnno = id.getAnnotation(DeleteBy.class);
            if (this.methods.contains(UIMethod.DELETE_BY_ID)) {
                if (deleteByAnno == null || deleteByAnno.onlyMulti()) {
                    throw new RuntimeException("mapper需要提供deleteById方法");
                }
                ByProperty byIdProperty = new ByProperty("deleteBy" + firstUpper(id.getName()), id.getName(), null,
                        id.getType().getCanonicalName(), "void", null);
                map.put("deleteByIdProperty", byIdProperty);
            }
            // 检查 deleteByIds
            if (this.methods.contains(UIMethod.DELETE_BY_IDS)) {
                if (deleteByAnno == null || !deleteByAnno.withMulti()) {
                    throw new RuntimeException("mapper需要提供deleteByIds方法");
                }
                String boxedType = getBoxedType(id.getType()).getCanonicalName();
                ByProperty byIdProperty = new ByProperty("deleteBy" + firstUpper(id.getName()) + "s", id.getName(), null,
                        "List<" + boxedType + ">", "void", null);
                byIdProperty.setBoxedType(boxedType);
                map.put("deleteByIdsProperty", byIdProperty);
            }
            // 查看是否需要Adapter方法
            RemoteSourceAdapter sourceAdapter = c.getAnnotation(RemoteSourceAdapter.class);
            if (sourceAdapter != null) {
                map.put("useAdapter", true);
                map.put("valueFieldGetMethod", get(sourceAdapter.valueField()));
                map.put("textFieldGetMethod", get(sourceAdapter.textField()));
                if(!sourceAdapter.pidField().equals("")){
                    map.put("treeAdapter", true);
                    map.put("pidFieldGetMethod", get(sourceAdapter.pidField()));
                    map.put("pidTopValue", sourceAdapter.pidTopValue());
                }
            }
            // 添加server赋值save方法
            if (this.methods.contains(UIMethod.SAVE)) {
                List<String> server_save_assignment = Arrays.stream(fields).filter(f -> f.getAnnotation(Input.class) != null
                        && (f.getAnnotation(Input.class).scope() == InputScope.SAVE || f.getAnnotation(Input.class).scope() == InputScope.BOTH)
                        && f.getAnnotation(Input.class).serverValue())
                        .map(f -> {
                            Input ui = f.getAnnotation(Input.class);
                            return ui.type().java(class_ss, f, ui);
                        }).collect(Collectors.toList());
                map.put("server_save_assignment", server_save_assignment);
            }
            // 添加server赋值update方法
            if (this.methods.contains(UIMethod.UPDATE)) {
                List<String> server_update_assignment = Arrays.stream(fields).filter(f -> f.getAnnotation(Input.class) != null
                        && (f.getAnnotation(Input.class).scope() == InputScope.UPDATE || f.getAnnotation(Input.class).scope() == InputScope.BOTH)
                        && f.getAnnotation(Input.class).serverValue())
                        .map(f -> {
                            Input ui = f.getAnnotation(Input.class);
                            return ui.type().java(class_ss, f, ui);
                        }).collect(Collectors.toList());
                map.put("server_update_assignment", server_update_assignment);
            }

            // 生成js命名空间下所有公共方法
            List<String> public_methods = new ArrayList<>();

            // 处理所有@LocalSource
            List<SourceProperty> local_sources = new ArrayList<>();
            for (LocalSource localSource : c.getAnnotationsByType(LocalSource.class)) {
                String kv_source = Arrays.stream(localSource.kv()).map(kv -> "\"" + kv.value() + "\":\"" + kv.text() + "\"")
                        .collect(Collectors.joining(","));
                local_sources.add(new SourceProperty(localSource.kvvar(), "{" + kv_source + "}"));

                String source = Arrays.stream(localSource.kv()).map(kv -> "{\"text\":\"" + kv.text() + "\",\"value\":" + kv.value() + "}")
                        .collect(Collectors.joining(","));
                local_sources.add(new SourceProperty(localSource.var(), "[" + source + "]"));
            }
            map.put("local_sources", local_sources);

            // 处理所有@RemoteSource
            List<String> requestFunc = new ArrayList<>();
            List<String> responseArg = new ArrayList<>();
            RemoteSource[] remoteSources = c.getAnnotationsByType(RemoteSource.class);
            for (RemoteSource remoteSource : remoteSources) {
                requestFunc.add("$.get(\"" + appPath + remoteSource.value() + "\")");
                responseArg.add(remoteSource.var());
            }
            if (remoteSources.length == 0) {
                map.put("remote_begin", "");
                map.put("remote_end", "");
            } else if (remoteSources.length == 1) {
                String remote_begin = "$.when(" + requestFunc.stream().collect(Collectors.joining(",")) + ").done(function(" + responseArg.stream().collect(Collectors.joining(",")) + "){";
                String remote_end = "});";
                map.put("remote_begin", remote_begin);
                map.put("remote_end", remote_end);
            } else { // 参数大于1
                List<String> list = Stream.iterate(1, i -> i + 1).limit(responseArg.size()).map(i -> "a" + i).collect(Collectors.toList());
                String args = list.stream().collect(Collectors.joining(","));
                StringBuilder remote_begin = new StringBuilder(255);
                remote_begin.append("$.when(").append(requestFunc.stream().collect(Collectors.joining(","))).append(").done(function(").append(args).append("){").append(System.lineSeparator());
                for (int i = 0; i < responseArg.size(); i++) {
                    remote_begin.append("\t\tvar ").append(responseArg.get(i)).append("=").append(list.get(i)).append("[0]").append(System.lineSeparator());
                }
                String remote_end = "});";
                map.put("remote_begin", remote_begin);
                map.put("remote_end", remote_end);
            }

            // 处理 @Search
            List<String> search_html = Arrays.stream(fields).filter(f -> f.getAnnotation(Search.class) != null && f.getAnnotation(FindByVo.class) != null).map(f -> {
                Search ui = f.getAnnotation(Search.class);
                FindByVo vo = f.getAnnotation(FindByVo.class);
                return ui.type().html(class_ss, f, ui, vo);
            }).collect(Collectors.toList());
            map.put("search_html", search_html);

            List<String> search_func = Arrays.stream(fields).filter(f -> f.getAnnotation(Search.class) != null && f.getAnnotation(FindByVo.class) != null).map(f -> {
                Search ui = f.getAnnotation(Search.class);
                FindByVo vo = f.getAnnotation(FindByVo.class);
                return ui.type().func(class_ss, f, ui, vo);
            }).collect(Collectors.toList());
            map.put("search_func", search_func);

            // 处理 @Input
            List<String> save_html = Arrays.stream(fields).filter(f -> f.getAnnotation(Input.class) != null
                    && (f.getAnnotation(Input.class).scope() == InputScope.SAVE || f.getAnnotation(Input.class).scope() == InputScope.BOTH)).map(f -> {
                Input ui = f.getAnnotation(Input.class);
                return ui.type().html(class_ss, f, ui, "save");
            }).collect(Collectors.toList());
            map.put("save_html", save_html);
            map.put("save_height", save_html.size() * 48 + 90);

            List<String> save_func = Arrays.stream(fields).filter(f -> f.getAnnotation(Input.class) != null
                    && (f.getAnnotation(Input.class).scope() == InputScope.SAVE || f.getAnnotation(Input.class).scope() == InputScope.BOTH)).map(f -> {
                Input ui = f.getAnnotation(Input.class);
                return ui.type().func(class_ss, f, ui, "save");
            }).collect(Collectors.toList());
            map.put("save_func", save_func);

            List<String> update_html = Arrays.stream(fields).filter(f -> f.getAnnotation(Input.class) != null
                    && (f.getAnnotation(Input.class).scope() == InputScope.UPDATE || f.getAnnotation(Input.class).scope() == InputScope.BOTH)).map(f -> {
                Input ui = f.getAnnotation(Input.class);
                return ui.type().html(class_ss, f, ui, "update");
            }).collect(Collectors.toList());
            map.put("update_html", update_html);
            map.put("update_height", update_html.size() * 48 + 90);

            List<String> update_func = Arrays.stream(fields).filter(f -> f.getAnnotation(Input.class) != null
                    && (f.getAnnotation(Input.class).scope() == InputScope.UPDATE || f.getAnnotation(Input.class).scope() == InputScope.BOTH)).map(f -> {
                Input ui = f.getAnnotation(Input.class);
                return ui.type().func(class_ss, f, ui, "update");
            }).collect(Collectors.toList());
            map.put("update_func", update_func);


            // 生成 formatter需要的函数
            List<FormatterProperty> datagrid_formatters = new ArrayList<>();
            // 处理 datagrid columns
            List<String> datagrid_columns = Arrays.stream(fields).filter(f -> f.getAnnotation(DatagridColumn.class) != null).map(f -> {
                DatagridColumn anno = f.getAnnotation(DatagridColumn.class);
                String funcName = EMPTY;
                if (FormatterType.NONE != anno.formatter().type()) {
                    funcName = f.getName() + "_formatter";
                    FormatterProperty fp = new FormatterProperty();
                    fp.setFuncName(funcName);
                    fp.setFuncBody(anno.formatter().type().funcBody(anno.formatter().value()));
                    datagrid_formatters.add(fp);
                    public_methods.add("\"" + funcName + "\":" + funcName);
                }
                return "title:'" + anno.title() + "',field:'" + f.getName() + "',width:" + anno.width() + ",sortable:" + anno.sortable()
                        + (EMPTY.equals(anno.align()) ? "" : ",align:'" + anno.align() + "'")
                        + (FormatterType.NONE == anno.formatter().type() ? "" : ",formatter:App." + class_ss + "." + funcName)
                        + (EMPTY.equals(anno.editor()) ? "" : ",editor:" + anno.editor() + "");
            }).collect(Collectors.toList());
            map.put("datagrid_columns", datagrid_columns);
            map.put("datagrid_formatters", datagrid_formatters);
            map.put("public_methods", public_methods.stream().collect(Collectors.joining(",")));

            if (!this.methods.contains(UIMethod.SAVE) && !this.methods.contains(UIMethod.DELETE_BY_IDS) && public_methods.size() == 0) {
                map.put("toolbar", false);
            }

            // 处理@Middle
            Middle[] middles = c.getAnnotationsByType(Middle.class);
            List<MiddleProperty> middleProperties = new ArrayList<>();
            StringBuilder operationHtml = new StringBuilder(256);
            operationHtml.append("\"datagrid_buttons\":function(value, row) {return \"");
            int operationCount = 0;
            for(Middle m:middles) {
                MiddleProperty mp = new MiddleProperty(m.table(), m.oppositeClass(), m.title(), m.ownColumn(), m.oppositeColumn());
                middleProperties.add(mp);
                operationHtml.append("<a href='javascript:void(0)' data-id='\"+row."+id.getName()+"+\"' class='button-edit oper-"+mp.getOp_ss()+"'>编辑"+mp.getTitle()+"</a> ");
                operationCount++;
            }
            map.put("middleProperties",middleProperties);
            if (this.methods.contains(UIMethod.UPDATE)){
                operationHtml.append("<a href='javascript:void(0)' data-id='\"+row."+id.getName()+"+\"' class='button-edit oper'>修改</a> ");
                operationCount++;
            }
            if(this.methods.contains(UIMethod.DELETE_BY_ID)){
                operationHtml.append("<a href='javascript:void(0)' data-id='\"+row."+id.getName()+"+\"' class='button-delete oper'>删除</a>");
                operationCount++;
            }
            operationHtml.append("\";}");
            map.put("operationHtml", operationHtml.toString());
            map.put("operationCount", operationCount);
            // 处理模板
            Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
            cfg.setTemplateLoader(new ClassTemplateLoader(MapperGenerator.class, "/template"));
            cfg.setDefaultEncoding("UTF-8");
            String path = this.structure.htmlOutputDir(this.outputDir, this.htmlDir);
            new File(path).mkdirs();
            Template t = cfg.getTemplate("html.ftl");
            t.process(map, new OutputStreamWriter(new FileOutputStream(path + map.get("class_ss") + ".html")));

            path = this.structure.javaOutputDir(this.outputDir, this.voPackage);
            new File(path).mkdirs();
            t = cfg.getTemplate("vo_datagrid.ftl");
            t.process(map, new OutputStreamWriter(new FileOutputStream(path + "DataGridVo.java")));

            t = cfg.getTemplate("vo_tree.ftl");
            t.process(map, new OutputStreamWriter(new FileOutputStream(path + "TreeVo.java")));

            path = this.structure.javaOutputDir(this.outputDir, this.controllerPackage);
            new File(path).mkdirs();
            t = cfg.getTemplate("controller.ftl");
            t.process(map, new OutputStreamWriter(new FileOutputStream(path + map.get("class_s") + "Controller.java")));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
